Day 14、15 的 RDB 跟 AOF,檔案都存在同一台機器的硬碟上。硬碟壞了、整台機器掛了,檔案一樣跟著沒。
主從複製就是多開一台 Redis,主節點寫進去的東西,自動複製一份到從節點。主節點負責寫,從節點跟著抄。
master_link_status(從節點跟主節點還連著嗎)Day 2 開的 redis30days 不動,在 Day 2 的 docker-compose.yml 旁邊另外寫一份 docker-compose-replica.yml:
# Day 16 主從複製:一主一從,跟 Day 2 的 redis30days 分開跑
name: day16 # 網路會叫 day16_default
services:
redis-master:
image: redis:7-alpine
container_name: redis-master
redis-replica:
image: redis:7-alpine
container_name: redis-replica
command: redis-server --replicaof redis-master 6379 # 當 redis-master 的從節點
這邊是在同一台電腦開兩個容器來模擬,正式環境的主從要放在不同機器上,不然電腦一掛還是一起消失。
兩台都沒有對外開 port,全部用 docker exec 操作,不會跟 redis30days 搶 6379。
先只開主節點,灌 10 萬筆資料(在主機的終端機下):
docker compose -f docker-compose-replica.yml up -d redis-master # -f 指定用哪一份 compose 檔
for i in $(seq 1 100000); do echo "SET user:$i $i"; done | docker exec -i redis-master redis-cli --pipe
再開從節點(在主機的終端機下):
docker compose -f docker-compose-replica.yml up -d redis-replica

從節點第一次連上,要先拿到主節點的全部資料。等 5 秒左右看主節點的 log(grep -i sync 只留跟同步有關的行,在主機的終端機下):
docker logs redis-master | grep -i sync

Full resync 就是全量同步。Starting BGSAVE 是 Day 14 那招:fork 一個副本拍一份 RDB,只是這次不寫硬碟,直接從網路傳給從節點。中間停的 5 秒左右,是在等看看還有沒有別台要一起同步。
再看從節點這邊(在主機的終端機下):
docker logs --tail 9 redis-replica

從節點先把自己原本的資料清空(Flushing old data),再載入主節點傳來的 RDB。keys loaded: 100000,10 萬筆都過來了。
看一下兩台現在的身分(在主機的終端機下):
docker exec redis-master redis-cli ROLE
docker exec redis-replica redis-cli ROLE


一台 master,一台 slave(從節點的舊稱)。主節點底下列著從節點的 IP,從節點這邊寫著跟的是 redis-master,connected 就是連上了。
在從節點寫一筆試試看(在主機的終端機下):
docker exec redis-replica redis-cli SET price 1

從節點預設唯讀,寫入只能找主節點。常見的用法是寫去主節點、讀去從節點,讓從節點分擔讀的流量,叫做讀寫分離。
主節點自己寫完就回 OK,不會等從節點,複製是之後才送過去的。平常一下子就傳到了,看不出來,所以把從節點的網路線拔掉,讓它傳不過去(在主機的終端機下):
docker exec redis-master redis-cli FLUSHALL # 先清空所有資料
docker exec redis-master redis-cli SET price 100
docker exec redis-master redis-cli SET coupon 50 EX 60 # 60 秒後過期,等等用
docker network disconnect day16_default redis-replica # 拔掉從節點的網路線
docker exec redis-master redis-cli SET price 200
docker exec redis-replica redis-cli GET price

主節點成功寫入 200,但從節點還是讀到 100。如果主節點這時候掛了,200 這筆就跟著沒了,從節點從來沒拿到過。
Day 12 說過,過期 key 只有主節點會刪。等 1 分鐘讓 coupon 過期,兩邊各看一次(在主機的終端機下):
docker exec redis-master redis-cli DBSIZE # 1(price)
docker exec redis-replica redis-cli EXISTS coupon # 0
docker exec redis-replica redis-cli DBSIZE # 2(price & coupon)
主節點已經刪了,剩 1 個 key。從節點說 coupon 不存在(0),DBSIZE 卻還是 2:過期了它會當作沒有,但不會自己刪,要等主節點的 DEL 傳過來。
從節點超過 60 秒沒收到主節點的消息,會自己判定斷線。多等一下,看到 down 再把網路線接回去(太早接回去,要等比較久才會補上,在主機的終端機下):
docker exec redis-replica redis-cli INFO replication | grep master_link_status

接回去,等幾秒再看主節點的 log(在主機的終端機下):
docker network connect day16_default redis-replica
docker logs redis-master | grep -i sync

這三行是:主節點先把逾時的從節點踢掉,從節點重連回來,Partial resynchronization(增量同步)只補斷線這段缺的 142 bytes(SET price 200、DEL coupon,加上主節點每 10 秒一次的 PING),沒有再 BGSAVE 一次。
從節點這邊也補上了(在主機的終端機下):
docker exec redis-replica redis-cli GET price # 200(price)
docker exec redis-replica redis-cli DBSIZE # 1
200 過來了,coupon 也被主節點的 DEL 刪掉了。
主節點會把最近的寫入留一份在記憶體(repl-backlog-size,預設 1MB),從節點回來就從這裡補。斷線期間寫超過 repl-backlog-size 大小,就只能再全量同步一次。
WAIT(等幾台從節點確認),但它只負責回報節點確認數量,沒等到也不會把寫入收回來。FLUSHALL,從節點上那 10 萬筆 user 也一起清掉了)。主節點沒開持久化又重開,會空著回來,從節點照樣全量同步,先清空自己再載入一份空的,兩台一起空掉。主節點掛了,從節點不會自己升上去,只會一直重連。明天來看 Sentinel,讓它自動挑一台從節點接手![]()